package main

import (
	"embed"
	"encoding/base64"
	"fmt"
	"io/fs"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path"
	"reflect"
	"strings"
	"unsafe"

	"github.com/gin-gonic/gin"
	"gopkg.in/yaml.v3"
)

// web 页面 basename 占位符
const WEB_BASENAME_PLACEHOLDER = "__ipset_manager_web_basename_placeholder__"

//go:embed static
var staticFS embed.FS //static目录嵌入staticFS变量

type ConfigIPSet struct {
	Setname     string `yaml:"setname" json:"setname"`
	Description string `yaml:"description" json:"description"`
	Disabled    bool   `yaml:"disabled" json:"disabled"`
}
type ConfigUser struct {
	Username    string `yaml:"username" json:"username"`
	Password    string `yaml:"password" json:"password"`
	Description string `yaml:"description" json:"description"`
	Disabled    bool   `yaml:"disabled" json:"disabled"`
}

type ConfigTLS struct {
	Enabled  bool   `yaml:"enabled" json:"enabled"`
	CertFile string `yaml:"cert_file" json:"cert_file"`
	KeyFile  string `yaml:"key_file" json:"key_file"`
}

type YamlConfig struct {
	Prefix   string        `yaml:"prefix" json:"prefix"` // 页面前缀路径
	Listen   string        `yaml:"listen" json:"listen"`
	IPSets   []ConfigIPSet `yaml:"ipsets" json:"ipsets"`
	Users    []ConfigUser  `yaml:"users" json:"users"`
	WebProxy string        `yaml:"web_proxy" json:"web_proxy"`
	TLS      ConfigTLS     `yaml:"tls" json:"tls"`
}
type Config struct {
	Prefix   string                 `yaml:"prefix" json:"prefix"` // 页面前缀路径
	Listen   string                 `yaml:"listen" json:"listen"`
	IPSets   map[string]ConfigIPSet `yaml:"ipsets" json:"ipsets"`
	Users    map[string]ConfigUser  `yaml:"users" json:"users"`
	WebProxy string                 `yaml:"web_proxy" json:"web_proxy"`
	TLS      ConfigTLS              `yaml:"tls" json:"tls"`
}

const IPSetKey = "ipset"

var config *Config = &Config{}

// 读取配置文件
func readConfig(c *Config) error {
	data, err := os.ReadFile("config.yaml")
	if err != nil {
		return err
	}
	yamlConf := YamlConfig{}
	err = yaml.Unmarshal(data, &yamlConf)
	if err != nil {
		return err
	}

	ipsets := make(map[string]ConfigIPSet)

	for _, ipset := range yamlConf.IPSets {
		if !ipset.Disabled {
			ipsets[ipset.Setname] = ipset
		}

	}

	users := make(map[string]ConfigUser)
	for _, user := range yamlConf.Users {
		if !user.Disabled {
			users[user.Username] = user
		}
	}
	*c = Config{
		IPSets:   ipsets,
		Users:    users,
		Listen:   yamlConf.Listen,
		Prefix:   yamlConf.Prefix,
		WebProxy: yamlConf.WebProxy,
		TLS:      yamlConf.TLS,
	}
	return nil
}

// 基础认证
func BasicAuth(conf *Config) gin.HandlerFunc {

	accounts := map[string]string{}
	for _, user := range conf.Users {
		base := user.Username + ":" + user.Password
		accounts["Basic "+base64.StdEncoding.EncodeToString([]byte(base))] = user.Username
	}

	return func(c *gin.Context) {
		// Search user in the slice of allowed credentials
		user, found := accounts[c.GetHeader("Authorization")]
		if !found {
			// Credentials doesn't match, we return 401 and abort handlers chain.
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
		// c.MustGet(gin.AuthUserKey).
		c.Set(gin.AuthUserKey, user)
	}
}

// 获取路径中的 setname
func HandleIPSet(conf *Config) gin.HandlerFunc {

	return func(c *gin.Context) {
		var path struct {
			Setname string `uri:"setname" binding:"required"`
		}
		if err := c.ShouldBindUri(&path); err != nil {
			c.AbortWithStatus(http.StatusBadRequest)
			return
		}

		ipset, ok := conf.IPSets[path.Setname]
		// 找不到或者禁用
		if !ok || ipset.Disabled {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}

		c.Set(IPSetKey, ipset)
	}

}

func GetStructPtrUnExportedField(source interface{}, fieldName string) reflect.Value {
	v := reflect.ValueOf(source).Elem().FieldByName(fieldName)
	return reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()
}

func SetStructPtrUnExportedStrField(source interface{}, fieldName string, fieldVal interface{}) (err error) {
	v := GetStructPtrUnExportedField(source, fieldName)
	rv := reflect.ValueOf(fieldVal)
	if v.Kind() != rv.Kind() {
		return fmt.Errorf("invalid kind: expected kind %v, got kind: %v", v.Kind(), rv.Kind())
	}

	v.Set(rv)
	return nil
}

// 修改静态资源
// 替换 资源页面中 web basename为 配置文件 prefix
func modifyStaticFS() {
	basename := config.Prefix
	if basename == "" || strings.Trim(basename, "/") == WEB_BASENAME_PLACEHOLDER {
		return
	}
	// files *[]file
	// 文件切片指针
	filesPointer := reflect.ValueOf(&staticFS).Elem().FieldByName("files")
	// 文件切片
	fileSlice := filesPointer.Elem()
	// 文件数
	fileCount := fileSlice.Len()
	// 文件类型
	fileType := fileSlice.Index(0).Type()
	// 新切片
	newFileSlice := reflect.MakeSlice(fileSlice.Type(), 0, fileCount)

	// 遍历文件去修改文件
	for i := 0; i < fileCount; i++ {
		isModified := false
		file := reflect.NewAt(fileType, unsafe.Pointer(fileSlice.Index(i).UnsafeAddr())).Elem()
		// 获取文件路径
		name := file.FieldByName("name").String()
		// .html .js .js.map 文件
		if strings.HasSuffix(name, ".html") || strings.HasSuffix(name, ".js") || strings.HasSuffix(name, ".js.map") {
			data := file.FieldByName("data")
			dataStr := data.String()
			// 文件内容包含占位符
			if strings.Contains(dataStr, WEB_BASENAME_PLACEHOLDER) {
				// 替换占位符
				newDataStr := strings.ReplaceAll(dataStr, WEB_BASENAME_PLACEHOLDER, basename)
				// 修改
				newFile := reflect.New(file.Type()).Elem()
				newFile.Set(file)

				canModifyData := reflect.NewAt(data.Type(), unsafe.Pointer(newFile.FieldByName("data").UnsafeAddr())).Elem()
				canModifyData.Set(reflect.ValueOf(newDataStr))

				newFileSlice = reflect.Append(newFileSlice, newFile)
				isModified = true
			}
		}

		if !isModified {
			newFileSlice = reflect.Append(newFileSlice, file)
		}
	}

	// 新文件切片指针
	newFilesPointer := reflect.New(newFileSlice.Type())
	// 将新切片设置到新切片指针中
	newFilesPointer.Elem().Set(newFileSlice)
	// 可以修改的文件切片指针
	canModifyFilesPointer := reflect.NewAt(filesPointer.Type(), unsafe.Pointer(filesPointer.UnsafeAddr())).Elem()
	// 设置新切片到对象
	canModifyFilesPointer.Set(newFilesPointer)
}

func main() {
	err := readConfig(config)
	if err != nil {
		log.Fatal(err)
	}

	handleAuth := BasicAuth(config)
	handleIPSet := HandleIPSet(config)

	r := gin.Default()

	router := r.Group(config.Prefix)
	web := router.Group("/web")
	var webHandler gin.HandlerFunc
	if config.WebProxy != "" {
		webHandler = func(c *gin.Context) {
			target, err := url.Parse(config.WebProxy)
			if err != nil {
				log.Fatal(err)
			}
			proxy := httputil.NewSingleHostReverseProxy(target)
			proxy.ServeHTTP(c.Writer, c.Request)
			c.Abort()
		}
	} else {
		modifyStaticFS()

		staticFS, err := fs.Sub(staticFS, "static")
		if err != nil {
			log.Fatal(err)
		}
		webFS := http.FS(staticFS)

		webHandler = func(c *gin.Context) {
			file := c.Param("filepath")
			f, err := webFS.Open(file)
			if err != nil {
				c.FileFromFS("/", webFS)
				return
			}
			defer f.Close()

			s, err := f.Stat()

			if err != nil {
				c.Status(http.StatusInternalServerError)
				return
			}

			if s.IsDir() {
				url := c.Request.URL.Path
				if url == "" || url[len(url)-1] != '/' {
					c.Redirect(http.StatusMovedPermanently, path.Base(url)+"/")
					return
				}
				c.Redirect(http.StatusMovedPermanently, path.Base(url)+"/index.html")
				return
			}
			c.FileFromFS(file, webFS)
		}
	}
	web.GET("/*filepath", webHandler)
	web.HEAD("/*filepath", webHandler)

	api := router.Group("/api")

	api.GET("/ip", func(c *gin.Context) {
		ip := c.RemoteIP()
		c.JSON(http.StatusOK, gin.H{
			"ipv4": ip,
		})
	})

	api.GET("/userinfo", handleAuth, func(c *gin.Context) {
		username := c.MustGet(gin.AuthUserKey).(string)
		user := config.Users[username]
		ip := c.RemoteIP()
		c.JSON(http.StatusOK, gin.H{
			"description": user.Description,
			"username":    user.Username,
			"ipv4":        ip,
		})
	})

	ipsetsApi := api.Group("/ipset", handleAuth)

	ipsetsApi.GET("/list", func(c *gin.Context) {
		c.JSON(http.StatusOK, config.IPSets)
	})

	ipsetApi := ipsetsApi.Group("/:setname", handleIPSet)

	ipsetApi.GET("/info", func(c *gin.Context) {
		ipset := c.MustGet(IPSetKey).(ConfigIPSet)
		ruleSet, err := GetRuleSet(ipset.Setname)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		c.JSON(http.StatusOK, ruleSet)
	})

	ipsetApi.POST("/add", func(c *gin.Context) {
		rule := Rule{}
		err := c.ShouldBindJSON(&rule)

		if err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		ipset := c.MustGet(IPSetKey).(ConfigIPSet)

		username := c.MustGet(gin.AuthUserKey).(string)

		user := config.Users[username]
		log.Printf("username: %s; %s is preparing to add rule IP: %s timeout: %d remarks: %s in %s\n", username, user.Description, rule.IP, rule.Timeout, rule.Remarks, ipset.Setname)

		err = rule.Apple(ipset.Setname)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		c.Status(http.StatusOK)
	})

	ipsetApi.POST("/add-current", func(c *gin.Context) {
		rule := Rule{}
		err := c.ShouldBindJSON(&rule)
		rule.IP = c.RemoteIP()

		ipset := c.MustGet(IPSetKey).(ConfigIPSet)

		username := c.MustGet(gin.AuthUserKey).(string)
		user := config.Users[username]
		log.Printf("username: %s; %s is preparing to add rule %s in %s\n", username, user.Description, rule.IP, ipset.Setname)

		err = rule.Apple(ipset.Setname)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		c.JSON(http.StatusOK, rule)
	})

	ipsetApi.DELETE("/del", func(c *gin.Context) {
		rule := Rule{}
		err := c.ShouldBindJSON(&rule)

		if err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		ipset := c.MustGet(IPSetKey).(ConfigIPSet)

		username := c.MustGet(gin.AuthUserKey).(string)
		user := config.Users[username]

		log.Printf("username: %s; %s is preparing to delete rule %s in %s\n", username, user.Description, rule.IP, ipset.Setname)

		err = rule.Remove(ipset.Setname)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		c.Status(http.StatusOK)
	})
	if config.TLS.Enabled {
		r.RunTLS(config.Listen, config.TLS.CertFile, config.TLS.KeyFile)
	} else {
		r.Run(config.Listen)
	}

}
